#pragma once

#include "sql_engine.h"

constexpr auto AND = " AND ";
constexpr auto OR = " OR ";
constexpr auto BRACKET_OPEN = "(";
constexpr auto BRACKET_CLOSE = ")";

constexpr auto SELECT = "SELECT ";
constexpr auto DISTINCT = "DISTINCT ";
constexpr auto FROM = " FROM ";
constexpr auto WHERE = " WHERE ";
constexpr auto ORDER_BY = " ORDER BY ";
constexpr auto ASC = "ASC";
constexpr auto DESC = "DESC";
constexpr auto GROUP_BY = " GROUP BY ";
constexpr auto LIMIT = " LIMIT ";
constexpr auto OFFSET = " OFFSET ";
constexpr auto JOIN = " INNER JOIN ";
constexpr auto OUTER_JOIN = " OUTER JOIN ";
constexpr auto LEFT_JOIN = " LEFT JOIN ";
constexpr auto RIGHT_JOIN = " RIGHT JOIN ";

constexpr auto UPDATE = "UPDATE ";
constexpr auto SET = "SET ";

constexpr auto INSERT = "INSERT ";
constexpr auto INTO = "INTO ";
constexpr auto VALUES = " VALUES ";

constexpr auto DEL = "DELETE ";

//////////////////////////////////////////////////////////////////////////
// ExprQuery
namespace sqlengine
{
    ExprQuery::ExprQuery()
    {
        m_being_open = false;
    }

    ExprQuery::~ExprQuery()
    {
    }

    ExprPtr ExprQuery::and_(std::string sentence)
    {
        build_sentence(AND, sentence);

        return shared_from_this();
    }

    ExprPtr ExprQuery::or_(std::string sentence)
    {
        build_sentence(OR, sentence);

        return shared_from_this();
    }

    ExprPtr ExprQuery::and_begin()
    {
        return build_condition_begin(AND);
    }

    ExprPtr ExprQuery::or_begin()
    {
        if (m_operation_before.find(AND) != m_operation_before.npos)
        {
            return build_condition_begin(AND);
        }
        return build_condition_begin(OR);
    }

    ExprPtr ExprQuery::end()
    {
        m_result.append(BRACKET_CLOSE);
        return shared_from_this();
    }

    ExprPtr ExprQuery::build_condition_begin(const char* condition)
    {
        m_being_open = true;
        m_result.append(condition);
        m_result.append(BRACKET_OPEN);
        return shared_from_this();
    }

    void ExprQuery::build_sentence(const char* condition, std::string& sentence)
    {
        if (m_result.empty() && !sentence.empty())
        {
            m_result.append(sentence);
        }
        else if (m_being_open)
        {
            m_being_open = false;
            m_result.append(sentence);
        }
        else
        {
            m_result.append(condition);
            m_result.append(sentence);
        }

        m_operation_before = condition;
    }

    std::string ExprQuery::to_string()
    {
        return m_result;
    }
}

//////////////////////////////////////////////////////////////////////////
// SelectQuery
namespace sqlengine
{
    SelectQuery::SelectQuery(std::string field)
        : m_distinct("")
        , m_limit(0)
        , m_offset(0)
    {
        if (!field.empty())
            m_fields.push_back(field);
    }

    SelectQuery::~SelectQuery()
    {
    }

    std::string SelectQuery::to_string()
    {
        std::string fields = get_string_fields();
        std::string from = get_string_from();
        std::string where = get_string_where();
        std::string order_by = get_string_order_by();
        std::string group_by = get_string_group_by();
        std::string limit = get_string_limit();
        std::string offset = get_string_offset();
        std::string join = get_string_join();
        std::string outer_join = get_string_outer_join();
        std::string left_join = get_string_left_join();
        std::string right_join = get_string_right_join();

        boost::format fmt("%1%%2%%3%%4%%5%%6%%7%%8%%9%%10%%11%%12%%13%%14%");
        fmt% SELECT
            % m_distinct
            % fields
            % FROM
            % from
            % join
            % outer_join
            % left_join
            % right_join
            % where
            % group_by
            % order_by
            % limit
            % offset;

        return fmt.str();
    }

    SelectPtr SelectQuery::distinct()
    {
        m_distinct = DISTINCT;
        return shared_from_this();
    }

    SelectPtr SelectQuery::field(std::string field)
    {
        if (!field.empty())
            m_fields.push_back(field);

        return shared_from_this();
    }

    SelectPtr SelectQuery::field(std::string field, std::string alias)
    {
        std::string s = "";

        if (!field.empty() && !alias.empty())
        {
            boost::format fmt("%s AS %s");
            fmt% field% alias;
            s = fmt.str();
        }

        return this->field(s);
    }

    SelectPtr SelectQuery::from(std::string from)
    {
        if (!from.empty())
            m_from.push_back(from);

        return shared_from_this();
    }

    SelectPtr SelectQuery::from(std::string from, std::string acronimus)
    {
        std::string s = "";

        if (!from.empty() && !acronimus.empty())
        {
            boost::format fmt("%s '%s'");
            fmt% from% acronimus;
            s = fmt.str();
        }

        return this->from(s);
    }

    SelectPtr SelectQuery::from(SelectPtr query, std::string acronimus)
    {
        std::string s = "";

        if (!query->to_string().empty() && !acronimus.empty())
        {
            boost::format fmt("%s%s%s");
            fmt % "(" % query->to_string() % ")";
            s = fmt.str();
        }

        return from(s, acronimus);
    }

    SelectPtr SelectQuery::where(std::string condition)
    {
        if (!condition.empty())
        {
            boost::format fmt("(%s)");
            fmt% condition;

            std::string s = fmt.str();
            m_where.push_back(s);
        }

        return shared_from_this();
    }

    SelectPtr SelectQuery::where(ExprPtr expr)
    {
        return where(expr->to_string());
    }

    ExprPtr SelectQuery::expr()
    {
        return std::make_shared<ExprQuery>();
    }

    SelectPtr SelectQuery::limit(int limit)
    {
        m_limit = limit;
        return shared_from_this();
    }

    SelectPtr SelectQuery::offset(int offset)
    {
        m_offset = offset;
        return shared_from_this();
    }

    SelectPtr SelectQuery::order_by(std::string order_by)
    {
        if (!order_by.empty())
        {
            if (m_order_by.size() > 0)
            {
                boost::format fmt("%s%s");
                fmt % ", " % order_by;

                std::string s = fmt.str();
                m_order_by.push_back(s);
            }
            else
            {
                m_order_by.push_back(order_by);
            }
        }

        return shared_from_this();
    }

    SelectPtr SelectQuery::asc()
    {
        m_order_by.push_back(ASC);
        return shared_from_this();
    }

    SelectPtr SelectQuery::desc()
    {
        m_order_by.push_back(DESC);
        return shared_from_this();
    }

    SelectPtr SelectQuery::group_by(std::string group_by)
    {
        if (!group_by.empty())
        {
            m_group_by.push_back(group_by);
        }

        return shared_from_this();
    }

    SelectPtr SelectQuery::join(std::string join)
    {
        if (!join.empty())
        {
            m_join.push_back(join);
        }

        return shared_from_this();
    }

    SelectPtr SelectQuery::join(std::string join, std::string alias)
    {
        std::string s = "";

        if (!join.empty() && !alias.empty())
        {
            boost::format fmt("%s %s");
            fmt% join% alias;
            s = fmt.str();
        }

        return this->join(s);
    }

    SelectPtr SelectQuery::join(std::string join, std::string alias, ExprPtr expr)
    {
        std::string s = "";

        if (!join.empty() && !alias.empty())
        {
            boost::format fmt("%s %s ON (%s)");
            fmt% join% alias% expr->to_string();
            s = fmt.str();
        }

        return this->join(s);
    }

    SelectPtr SelectQuery::outer_join(std::string outer_join)
    {
        m_outer_join.push_back(outer_join);
        return shared_from_this();
    }

    SelectPtr SelectQuery::left_join(std::string left_join, std::string on_left_join)
    {
        if (!left_join.empty() && !on_left_join.empty())
        {
            boost::format fmt("%s ON (%s)");
            fmt% left_join% on_left_join;
            std::string s = fmt.str();
            m_left_join.push_back(s);
        }

        return shared_from_this();
    }

    SelectPtr SelectQuery::left_join(std::string left_join, std::string alias, std::string on_left_join)
    {
        if (!left_join.empty() && !alias.empty() && !on_left_join.empty())
        {
            boost::format fmt("%s AS %s");
            fmt% left_join% alias;
            return this->left_join(fmt.str(), on_left_join);
        }

        return shared_from_this();
    }

    SelectPtr SelectQuery::right_join(std::string right_join, std::string on_right_join)
    {
        if (!right_join.empty() && !on_right_join.empty())
        {
            boost::format fmt("%s ON (%s)");
            fmt% right_join% on_right_join;
            std::string s = fmt.str();
            m_right_join.push_back(s);
        }

        return shared_from_this();
    }

    SelectPtr SelectQuery::right_join(std::string right_join, std::string alias, std::string on_right_join)
    {
        if (!right_join.empty() && !alias.empty() && !on_right_join.empty())
        {
            boost::format fmt("%s %s");
            fmt% right_join% alias;
            return this->right_join(fmt.str(), on_right_join);
        }

        return shared_from_this();
    }

    std::string SelectQuery::get_string_fields()
    {
        return m_fields.size() <= 0 ? "*" : boost::join(m_fields, ", ");
    }

    std::string SelectQuery::get_string_from()
    {
        return boost::join(m_from, ", ");
    }

    std::string SelectQuery::get_string_limit()
    {
        return m_limit <= 0 ? "" : (boost::format("%s%d") % LIMIT % m_limit).str();
    }

    std::string SelectQuery::get_string_offset()
    {
        return m_offset <= 0 ? "" : (boost::format("%s%d") % OFFSET % m_offset).str();
    }

    std::string SelectQuery::get_string_order_by()
    {
        return m_order_by.size() <= 0 ? "" : (boost::format("%s%s") % ORDER_BY % boost::join(m_order_by, " ")).str();
    }

    std::string SelectQuery::get_string_group_by()
    {
        std::string s = boost::join(m_group_by, ", ");
        return s.empty() ? "" : (boost::format("%s%s") % GROUP_BY % s).str();
    }

    std::string SelectQuery::get_string_where()
    {
        std::string s = boost::join(m_where, " AND ");
        return s.empty() ? "" : (boost::format("%s%s") % WHERE % s).str();
    }

    std::string SelectQuery::get_string_join()
    {
        std::string ret;
        for (auto s : m_join)
        {
            ret += (boost::format(" %s%s") % JOIN % s).str();
        }
        return ret;
    }

    std::string SelectQuery::get_string_outer_join()
    {
        std::string ret;
        for (auto s : m_outer_join)
        {
            ret += (boost::format(" %s%s") % OUTER_JOIN % s).str();
        }
        return ret;
    }

    std::string SelectQuery::get_string_left_join()
    {
        std::string ret;
        for (auto s : m_left_join)
        {
            ret += (boost::format(" %s%s") % LEFT_JOIN % s).str();
        }
        return ret;
    }

    std::string SelectQuery::get_string_right_join()
    {
        std::string ret;
        for (auto s : m_right_join)
        {
            ret += (boost::format(" %s%s") % RIGHT_JOIN % s).str();
        }
        return ret;
    }
}

//////////////////////////////////////////////////////////////////////////
// UpdateQuery
namespace sqlengine
{
    UpdateQuery::UpdateQuery()
    {
        m_table.clear();
        m_set.clear();
        m_where.clear();
    }

    UpdateQuery::~UpdateQuery()
    {
    }

    UpdatePtr UpdateQuery::table(std::string table)
    {
        boost::format fmt("%s ");
        fmt% table;
        m_table = fmt.str();

        return shared_from_this();
    }

    UpdatePtr UpdateQuery::set(std::string field, int value)
    {
        boost::format fmt("%1%=%2%");
        fmt
            % field
            % value;

        std::string s = fmt.str();
        m_set.push_back(s);

        return shared_from_this();
    }

    UpdatePtr UpdateQuery::set(std::string field, double value)
    {
        boost::format fmt("%1%=%2%");
        fmt
            % field
            % value;

        std::string s = fmt.str();
        m_set.push_back(s);

        return shared_from_this();
    }

    UpdatePtr UpdateQuery::set(std::string field, std::string value)
    {
        boost::format fmt("%1%='%2%'");
        fmt
            % field
            % value;

        std::string s = fmt.str();
        m_set.push_back(s);

        return shared_from_this();
    }

    UpdatePtr UpdateQuery::where(std::string where)
    {
        boost::format fmt("(%1%)");
        fmt% where;

        std::string s = fmt.str();
        m_where.push_back(s);

        return shared_from_this();
    }

    std::string UpdateQuery::get_string_where()
    {
        std::string s = boost::join(m_where, " AND ");
        return s.empty() ? "" : (boost::format("%s%s") % WHERE % s).str();
    }

    std::string UpdateQuery::get_string_set()
    {
        std::string s = boost::join(m_set, ", ");
        return s.empty() ? "" : (boost::format("%s%s") % SET % s).str();
    }

    std::string UpdateQuery::to_string()
    {
        std::string where = get_string_where();
        std::string set = get_string_set();

        boost::format fmt("%1%%2%%3%%4%");
        fmt% UPDATE
            % m_table
            % set
            % where;

        return fmt.str();
    }
}

//////////////////////////////////////////////////////////////////////////
// InsertQuery
namespace sqlengine
{
    InsertQuery::InsertQuery()
    {
        m_into.clear();
        m_fields.clear();
        m_values.clear();
    }

    InsertQuery::~InsertQuery()
    {
    }

    InsertPtr InsertQuery::into(std::string into)
    {
        boost::format fmt("%s ");
        fmt% into;
        m_into = fmt.str();

        return shared_from_this();
    }

    InsertPtr InsertQuery::set(std::string field, int value)
    {
        m_fields.push_back(field);
        m_values.push_back(std::to_string(value));

        return shared_from_this();
    }

    InsertPtr InsertQuery::set(std::string field, double value)
    {
        m_fields.push_back(field);
        m_values.push_back(std::to_string(value));

        return shared_from_this();
    }

    InsertPtr InsertQuery::set(std::string field, std::string value)
    {
        m_fields.push_back(field);
        
        if (value == "?")
            m_values.push_back(value);
        else
            m_values.push_back("'" + value + "'");

        return shared_from_this();
    }

    std::string InsertQuery::get_string_fields()
    {
        std::string s = boost::join(m_fields, ", ");

        boost::format fmt("(%1%)");
        fmt% s;
        return fmt.str();
    }

    std::string InsertQuery::get_string_values()
    {
        std::string s = boost::join(m_values, ", ");
        boost::format fmt("(%1%)");
        fmt% s;
        return fmt.str();
    }

    std::string InsertQuery::to_string()
    {
        std::string fields = get_string_fields();
        std::string values = get_string_values();

        boost::format fmt("%1%%2%%3%%4%%5%%6%");
        fmt% INSERT
            % INTO
            % m_into
            % fields
            % VALUES
            % values;

        return fmt.str();
    }
}

//////////////////////////////////////////////////////////////////////////
// DeleteQuery
namespace sqlengine
{
    DeleteQuery::DeleteQuery()
    {
        m_from.clear();
        m_where.clear();
    }

    DeleteQuery::~DeleteQuery()
    {
    }

    DeletePtr DeleteQuery::from(std::string from)
    {
        m_from = from;
        return shared_from_this();
    }

    DeletePtr DeleteQuery::where(std::string where)
    {
        boost::format fmt("(%1%)");
        fmt% where;

        std::string s = fmt.str();
        m_where.push_back(s);

        return shared_from_this();
    }

    std::string DeleteQuery::get_string_where()
    {
        std::string s = boost::join(m_where, " AND ");
        return s.empty() ? "" : (boost::format("%s%s") % WHERE % s).str();
    }

    std::string DeleteQuery::to_string()
    {
        std::string where = get_string_where();

        boost::format fmt("%1%%2%%3%%4%");
        fmt% DEL
            % FROM
            % m_from
            % where;

        return fmt.str();
    }
}

//////////////////////////////////////////////////////////////////////////
// SQLEngine
namespace sqlengine
{
    SQL::SQL()
        : m_query_select(nullptr)
        , m_query_update(nullptr)
        , m_query_insert(nullptr)
        , m_query_delete(nullptr)
        , m_query_expr(nullptr)
    {}

    SQL::~SQL()
    {}

    void SQL::clear()
    {
        m_query_select.reset();
        m_query_update.reset();
        m_query_insert.reset();
        m_query_delete.reset();
        m_query_expr.reset();
    }

    std::string SQL::to_string()
    {
        if (m_query_select)
            return m_query_select->to_string();
        else if (m_query_update)
            return m_query_update->to_string();
        else if (m_query_insert)
            return m_query_insert->to_string();
        else if (m_query_delete)
            return m_query_delete->to_string();
        else if (m_query_expr)
            return m_query_expr->to_string();
        else
            return "";
    }

    std::shared_ptr<SelectQuery> SQL::select(std::string field)
    {
        clear();
        m_query_select = std::make_shared<SelectQuery>(field);
        return m_query_select;
    }

    std::shared_ptr<UpdateQuery> SQL::update()
    {
        clear();
        m_query_update = std::make_shared<UpdateQuery>();
        return m_query_update;
    }

    std::shared_ptr<InsertQuery> SQL::insert()
    {
        clear();
        m_query_insert = std::make_shared<InsertQuery>();
        return m_query_insert;
    }

    std::shared_ptr<DeleteQuery> SQL::del()
    {
        clear();
        m_query_delete = std::make_shared<DeleteQuery>();
        return m_query_delete;
    }

    std::shared_ptr<ExprQuery> SQL::expr()
    {
        clear();
        m_query_expr = std::make_shared<ExprQuery>();
        return m_query_expr;
    }
}